// 13.1 拷贝、赋值与销毁
/**
 * 我们将以最基本的操作——拷贝构造函数、拷贝赋值运算符和析构函数作为开始。我们在13.6节（第470页）中将介绍移动操作（新标准所引入的操作）。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

// 演示拷贝构造函数
class Foo
{
public:
    Foo();            // 默认构造函数
    Foo(const Foo &); // 拷贝构造函数
};

int main()
{
    // 13.1.1 拷贝构造函数
    // 如果一个构造函数的第一个参数是自身类类型的引用，且任何额外参数都有默认值，则此构造函数是拷贝构造函数。[插图]
    Foo foo; // 一个展示拷贝构造函数的类
    // 拷贝构造函数的第一个参数必须是一个引用类型
    // 拷贝构造函数在几种情况下都会被隐式地使用。因此，拷贝构造函数通常不应该是explicit的（参见7.5.4节，第265页）。

    // 合成拷贝构造函数
    // 如果我们没有为一个类定义拷贝构造函数，编译器会为我们定义一个。
    // 一般情况，合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中（参见7.1.5节，第239页）。
    // 编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。
    // 每个成员的类型决定了它如何拷贝：对类类型的成员，会使用其拷贝构造函数来拷贝；内置类型的成员则直接拷贝。
    // 虽然我们不能直接拷贝一个数组（参见3.5.1节，第102页），但合成拷贝构造函数会逐元素地拷贝一个数组类型的成员。如果数组元素是类类型，则使用元素的拷贝构造函数来进行拷贝。

    // 拷贝初始化
    // 当使用直接初始化时，我们实际上是要求编译器使用普通的函数匹配（参见6.4节，第209页）来选择与我们提供的参数最匹配的构造函数。
    // 当我们使用拷贝初始化（copy initialization）时，我们要求编译器将右侧运算对象拷贝到正在创建的对象中，
    // 如果需要的话还要进行类型转换（参见7.5.4节，第263页）。
    string dots(10, 'c');               // 直接初始化
    string s(dots);                     // 直接初始化
    string s2 = dots;                   // 拷贝初始化
    string null_book = "9-999-99999-9"; // 拷贝初始化
    string nines = string(10, '9');     // 拷贝初始化
    // 拷贝初始化通常使用拷贝构造函数来完成。
    // 拷贝初始化不仅在我们用=定义变量时会发生，在下列情况下也会发生
    // 1. · 将一个对象作为实参传递给一个非引用类型的形参
    // 2. · 从一个返回类型为非引用类型的函数返回一个对象
    // 3. · 用花括号列表初始化一个数组中的元素或一个聚合类中的成员（参见7.5.5节，第266页）

    // 参数和返回值
    // 在函数调用过程中，具有非引用类型的参数要进行拷贝初始化（参见6.2.1节，第188页）。
    // 类似的，当一个函数具有非引用的返回类型时，返回值会被用来初始化调用方的结果（参见6.3.2节，第201页）。

    // 拷贝初始化的限制
    // 如果我们使用的初始化值要求通过一个explicit的构造函数来进行类型转换（参见7.5.4节，第265页），
    // 那么使用拷贝初始化还是直接初始化就不是无关紧要的了：[插图]
    vector<int> v1(10); // 正确，直接初始化
    // vector<int> v2 = 10; // 错误，vector的拷贝构造函数是explicit的
    void f(vector<int>); // f的参数进行拷贝初始化
    // f(10); // 错误，不能用一个explicit的构造函数拷贝一个实参
    f(vector<int>(10)); // 正确，从一个int构造一个临时vector
    // 如果我们希望使用一个explicit构造函数，就必须显式地使用，像此代码中最后一行那样。

    // 编译器可以绕过拷贝构造函数
    // 在拷贝初始化过程中，编译器可以（但不是必须）跳过拷贝/移动构造函数，直接创建对象。即，编译器被允许将下面的代码[插图]
    string null_book1 = "9-999-99999-9"; // 拷贝初始化
    // 改写为[插图]
    string null_book2("9-999-99999-9"); // 编译器略过了拷贝构造函数

    return 0;
}